package servlet;

import java.io.IOException;

import objects.MessageCodes;
import objects.ObjectCodes;
import objects.cluster.ClusterInfo;
import objects.cluster.ClusterLoginData;
import objects.cluster.ClusterStatusData;
import objects.cluster.MyJobData;
import objects.shared.Message;
import objects.shared.WorkPacket;
import objects.user.UserLoginData;
import objects.user.RegisterData;
import objects.user.UserInfo;

import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.websocket.WebSocket;

import actions.ClientFunctions;
import actions.ClusterFunctions;

import database.Database;
import filesystem.FileSystem;

import predictors.SubmitJob;
import utils.Utils;

public class SireWebSocket implements WebSocket, ObjectCodes, MessageCodes {
	private String uname;
	private String hostname;
	private Outbound out = null;
	private UserInfo userInfo;
	private ClusterInfo clusterInfo;
	private boolean login;
	private boolean isCluster = false;
	private Thread ping;

	public SireWebSocket(String hostname) {
		this.hostname = hostname;
	}

	public void onConnect(Outbound outbound) {
		out = outbound;
		login = false;
		ping = new Ping();
	}

	public void onMessage(byte frame, byte[] data, int offset, int length) {
		onMessage(frame, TypeUtil.toHexString(data, offset, length));
	}

	public void onMessage(byte frame, String data) {
		try {
			
			String[] tokens = data.split("\\|");
			byte obj_type = Byte.parseByte(tokens[0]);
			if(obj_type==PING)
				return;
			String json = null;
			if(tokens.length > 1) {
				json = tokens[1];
			}
			Utils.out("[RECEIVED]");
			Utils.out("----------");
			for (String s : tokens) {
				Utils.out(s);
			}
			Utils.out("----------");
			
			
			if (!login) {
				switch (obj_type) {
					case CLI_REGISTER: {
						Message res = ClientFunctions.registerUser(RegisterData.fromJSon(json));
						sendMessage(res);
						break;
					}
					case CLI_LOGIN: {
						clientLogin(json);
						break;
					}
					case CLU_LOGIN: {
						clusterLogin(json);
						break;
					}
				}
			} else {
				switch (obj_type) {
					case FS: {
						userInfo.fs = FileSystem.fromJson(json);
						Database.updateFilesystem(userInfo);
					}
					case CLI_WORK_PKT: {
						Utils.exec.execute(new SubmitJob(userInfo,WorkPacket.fromJSon(json)));
						break;
	
					}
					case MSG: {
						Message m = Message.fromJson(json);
						if(isCluster) {
							if (userInfo.isConnected) {
								userInfo.sendMessage(m);
							}
						} else {
							ClusterInfo cinfo = userInfo.clusterMap.get(m.toHost);
							if (cinfo != null && cinfo.isConnected) {
								cinfo.sendMessage(m);
							}
						}
						break;
					}
					case CLI_LOGOUT: {
						userInfo.isConnected = false;
						Utils.out("LOGOUT "+ Database.users.get(uname).isConnected);
						login = false;
						//ui.sendMessage(new Response(true, OK));
						break;
					}
					case CLU_ST_INFO:
					{
						Database.addClusterStatusInfo(clusterInfo.db_id,ClusterStatusData.fromJSon(json));
						break;
					}
					case CLU_JOB_INFO:
					{
						Database.addMyJobsInfo(hostname,MyJobData.fromJSon(json));
						break;
					}
				}
			}
		} catch (IOException ioe) {
			ioe.printStackTrace();
			onDisconnect();
		} catch (Exception e) {
			e.printStackTrace();
			Utils.LOG.warn(e.getMessage());
		}
	}

	private void clusterLogin(String json) throws Exception, IOException {
		Object[] objs = ClusterFunctions.clusterLogin(ClusterLoginData.fromJson(json), hostname);
		Message res = (Message) objs[0];
		if (res.type == OK) {
			userInfo = (UserInfo) objs[1];
			clusterInfo = (ClusterInfo) objs[2];
			login = true;
			clusterInfo.out = this.out;
			clusterInfo.isConnected = true;
			isCluster = true;
			ping.start();
			if(userInfo.isConnected) {
				userInfo.sendClusterInfo();
			}
		}
		sendMessage(res);
	}

	private void clientLogin(String json) throws IOException, Exception {
		Object o = ClientFunctions.userLogin(UserLoginData.fromJSon(json));
		if (o instanceof Message)
			sendMessage(((Message) o));
		else {
			userInfo = (UserInfo) o;
			userInfo.out = this.out;
			userInfo.sendLoginData();
			login = true;
			userInfo.isConnected = true;
			ping.start();
		}
	}
	
	private void sendMessage(Object o) throws IOException {
		if(out != null) {
			out.sendMessage(o.toString());
		}
	}

	public void onDisconnect() {

		if (login) {
			login = false;
			if (!isCluster) {
				Utils.out("\n+ User "+userInfo.username+": DISCONNECTED\n");
				userInfo.isConnected = false;
				userInfo.disconnect();
			} else {
				Utils.out("\n+ Cluster "+clusterInfo.local_hostname+": DISCONNECTED\n");
				userInfo.clusterMap.remove(clusterInfo.db_id);
				if (userInfo.isConnected) {
					try {
						userInfo.sendClusterInfo();
						/*userInfo.sendMessage(new Message(MessageCodes.CLUSTER_OFF,
								clusterInfo.hostname,clusterInfo.local_hostname));*/
					} catch (Exception e) {
					}
				}
				clusterInfo.isConnected = false;
			}
		}
	}

	public void onFragment(boolean more, byte opcode, byte[] data, int offset,
			int length) {
	}
	
	class Ping extends Thread {
		public void run() {
			while (true)
				try {
					Thread.sleep(60*1000);
					if(!login) break;
					if(!isCluster)
						userInfo.sendClusterInfo();
					else
						clusterInfo.sendMessage(PING+"|0");
				}catch(Exception e) {return;}
		}
	}
}
